/**
 * <p>Title: Snake</p>
 * <p>Copyright: (C) 2011 Gavin's Snake project. Licensed under the Apache License, Version 2.0 (the "License")</p>
 * @author Gavin
 */

package com.deaboway.snake;

import java.util.Random;
import java.util.Vector;

import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Font;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;

import com.deaboway.snake.util.Bundle;
import com.deaboway.snake.util.Log;
import com.deaboway.snake.util.Utils;

public class SnakeView extends TileView {

	// 游戏状态，默认值是准备状态
	private int mMode = READY;

	// 游戏的四个状态 暂停 准备 运行 和 失败
	public static final int PAUSE = 0;
	public static final int READY = 1;
	public static final int RUNNING = 2;
	public static final int LOSE = 3;

	// 游戏中蛇的前进方向，默认值北方
	private int mDirection = NORTH;
	// 下一步的移动方向，默认值北方
	private int mNextDirection = NORTH;

	// 游戏方向设定 北 南 东 西
	private static final int NORTH = 1;
	private static final int SOUTH = 2;
	private static final int EAST = 3;
	private static final int WEST = 4;

	// 三种游戏元
	private static final int RED_STAR = 1;
	private static final int YELLOW_STAR = 2;
	private static final int GREEN_STAR = 3;
	private static Image imageRED_STAR;
	private static Image imageYELLOW_STAR;
	private static Image imageGREEN_STAR;

	// 游戏得分
	private long mScore = 0;

	// 移动延迟
	private long mMoveDelay = 600;

	// 最后一次移动时的毫秒时刻
	private long mLastMove;

	// 显示游戏状态的文本组件
	// @J2ME 直接用String代替TextView
	private String mStatusText;

	// 蛇身数组(数组以坐标对象为元素)
	// @J2ME 用Vector替换ArrayList
	private Vector mSnakeTrail = new Vector();

	// 苹果数组(数组以坐标对象为元素)
	// @J2ME 用Vector替换ArrayList
	private Vector mAppleList = new Vector();

	// 随机数
	private static final Random RNG = new Random();

	// 创建一个Refresh Handler来产生动画： 通过sleep()来实现
	// @J2ME 没有Handler，直接用Thread定期repaint()就OK
	private RefreshHandler mRedrawHandler = new RefreshHandler();
	
	class RefreshHandler extends Thread {
        public void run() {
            while (true) {
                try {
                	//delay一个延迟时间单位
                    Thread.sleep(mMoveDelay);
                } catch (Exception e) {
                    e.printStackTrace();
                }
				// 更新View对象
				SnakeView.this.update();
				// 强制重绘
				SnakeView.this.repaint();
            }
        }
	};

	// 构造函数
	// @J2ME 资源和配置不再从XML文件中获取，直接在构造函数中配置好
	public SnakeView() {
		// 构造时初始化
		initSnakeView();
	}

	// 初始化
	private void initSnakeView() {
		// 获取图片资源
		try {
			imageRED_STAR = Image.createImage("/redstar.png");
			imageRED_STAR = Utils.zoomImage(imageRED_STAR,mTileSize,mTileSize);
			imageYELLOW_STAR = Image.createImage("/yellowstar.png");
			imageYELLOW_STAR = Utils.zoomImage(imageYELLOW_STAR,mTileSize,mTileSize);
			imageGREEN_STAR = Image.createImage("/greenstar.png");
			imageGREEN_STAR = Utils.zoomImage(imageGREEN_STAR,mTileSize,mTileSize);
		} catch(Exception e) {
			Log.info("Create Images: "+e);
		}

		// 设置贴片图片数组
		resetTiles(4);

		// 把三种图片存到Bitmap对象数组
		loadTile(RED_STAR, imageRED_STAR);
		loadTile(YELLOW_STAR, imageYELLOW_STAR);
		loadTile(GREEN_STAR, imageGREEN_STAR);
	}

	// 开始新的游戏——初始化
	private void initNewGame() {
		// 清空Vector列表
		mSnakeTrail.removeAllElements();
		mAppleList.removeAllElements();

		// 创建蛇身

		mSnakeTrail.addElement(new Coordinate(7, 7));
		mSnakeTrail.addElement(new Coordinate(6, 7));
		mSnakeTrail.addElement(new Coordinate(5, 7));
		mSnakeTrail.addElement(new Coordinate(4, 7));
		mSnakeTrail.addElement(new Coordinate(3, 7));
		mSnakeTrail.addElement(new Coordinate(2, 7));

		// 新的方向 ：北方
		mNextDirection = NORTH;

		// 2个随机位置的苹果
		addRandomApple();
		addRandomApple();

		// 移动延迟
		mMoveDelay = 600;
		// 初始得分0
		mScore = 0;
	}
	
	// 坐标数组转整数数组，把Coordinate对象的x y放到一个int数组中——用来保存状态
	private String coordVectorToString(Vector cvec) {
		int count = cvec.size();
		StringBuffer rawArray = new StringBuffer();
		for (int index = 0; index < count; index++) {
			Coordinate c = (Coordinate) cvec.elementAt(index);
			rawArray.append(c.x+",");
			rawArray.append(c.y+",");
			Log.info("coordVectorToString(), c.x="+c.x+",c.y="+c.y);
		}
		Log.info("coordVectorToString(), rawArray.toString="+rawArray);
		return rawArray.toString();
	}

	// 保存状态
	// @J2ME 没有Android那种方便的保存状态的Bundle，只能用RMS了
	public void saveState() {
		Log.info("saveState(), mAppleList="+coordVectorToString(mAppleList));
		Bundle.getInstance().setValue(0, coordVectorToString(mAppleList));
		Log.info("saveState(), mDirection="+mDirection);
		Bundle.getInstance().setValue(1, mDirection+"");
		Log.info("saveState(), mNextDirection="+mNextDirection);
		Bundle.getInstance().setValue(2, mNextDirection+"");
		Log.info("saveState(), mMoveDelay="+mMoveDelay);
		Bundle.getInstance().setValue(3, mMoveDelay+"");
		Log.info("saveState(), mScore="+mScore);
		Bundle.getInstance().setValue(4, mScore+"");
		Log.info("saveState(), mSnakeTrail="+coordVectorToString(mSnakeTrail));
		Bundle.getInstance().setValue(5, coordVectorToString(mSnakeTrail));
		try {
			Bundle.getInstance().updateBundles();
		} catch (Exception e) {
			Log.info(e.toString());
		}
	}

	// 整数数组转坐标数组，把一个int数组中的x y放到Coordinate对象数组中——用来恢复状态
	// @J2ME 还是用Vector替换ArrayList
	private Vector coordStringToVector(String raw) {
		Vector coordArrayList = new Vector();
		Log.info("coordStringToVector(), raw="+raw);
		String[] rawArray = Utils.splitUtil(raw,",");
		Log.info("coordStringToVector(), rawArray.length="+rawArray.length);
		int coordCount = rawArray.length;
		for (int index = 0; index < coordCount; index += 2) {
			Coordinate c = new Coordinate(Integer.parseInt(rawArray[index]), Integer.parseInt(rawArray[index + 1]));
			coordArrayList.addElement(c);
		}
		return coordArrayList;
	}

	// 恢复状态
	// @J2ME 用rms实现Bundle
	public boolean restoreState() {

		setMode(PAUSE);

		try {
			Log.info("restoreState(), mAppleList0="+Bundle.getInstance().getValue(0));
			mAppleList = coordStringToVector(Bundle.getInstance().getValue(0));
			Log.info("restoreState(), mAppleList="+mAppleList);
			mDirection = Integer.parseInt(Bundle.getInstance().getValue(1));
			Log.info("restoreState(), mDirection="+mDirection);
			mNextDirection = Integer.parseInt(Bundle.getInstance().getValue(2));
			Log.info("restoreState(), mNextDirection="+mNextDirection);
			mMoveDelay = Long.parseLong(Bundle.getInstance().getValue(3));
			Log.info("restoreState(), mMoveDelay="+mMoveDelay);
			mScore = Long.parseLong(Bundle.getInstance().getValue(4));
			Log.info("restoreState(), mScore="+mScore);
			mSnakeTrail = coordStringToVector(Bundle.getInstance().getValue(5));
			Log.info("restoreState(), mSnakeTrail="+mSnakeTrail);
		} catch(Exception e) {
			// 重置RMS，获取默认值
			Log.error("restoreState():"+e);
			Bundle.getInstance().resetBundles();
			
			initNewGame();
			
			return false;
		}
		return true;
	}

	// 监听用户键盘操作，并处理这些操作
	// 按键事件处理，确保贪吃蛇只能90度转向，而不能180度转向
	// @J2ME keyPressed代替onKeyDown
	protected void keyPressed(int keyCode) {
		Log.debug("keyPressed:keyCode="+keyCode);
		int action = getGameAction(keyCode);
		Log.debug("keyPressed:action="+action);

		// 向上键
		if (action == Canvas.UP) {
			// 准备状态或者失败状态时
			if (mMode == READY | mMode == LOSE) {
				// 初始化游戏
				Log.debug("keyPressed: mMode="+mMode);
				initNewGame();
				// 设置游戏状态为运行
				Log.debug("keyPressed: before setMode() mMode="+mMode);
				setMode(RUNNING);
				// 更新
				Log.debug("keyPressed: before update() mMode="+mMode);
				update();
			}

			// 暂停状态时
			if (mMode == PAUSE) {
				// 设置成运行状态
				setMode(RUNNING);
				update();
			}

			// 如果是运行状态时，如果方向原有方向不是向南，那么方向转向北
			if (mDirection != SOUTH) {
				mNextDirection = NORTH;
			}
		}

		// 向下键
		if (action == Canvas.DOWN) {
			// 原方向不是向上时，方向转向南
			if (mDirection != NORTH) {
				mNextDirection = SOUTH;
			}
		}

		// 向左键
		if (action == Canvas.LEFT) {
			// 原方向不是向右时，方向转向西
			if (mDirection != EAST) {
				mNextDirection = WEST;
			}
		}

		// 向右键
		if (action == Canvas.RIGHT) {
			// 原方向不是向左时，方向转向东
			if (mDirection != WEST) {
				mNextDirection = EAST;
			}
		}

		// TODO
		// @J2ME 需要实现其它键，如暂停恢复，退出
		if (action == Canvas.FIRE) {
			switch(mMode) {
			case RUNNING:
				setMode(PAUSE);
				break;
			case PAUSE:
			case READY:
			case LOSE:
				saveState();
				Snake.SNAKE.notifyDestroyed();
			}
		}
		
	}


	// 设置状态显示View
	// @J2ME String代替TextView
	public void setTextView(String newView) {
		mStatusText = newView;
	}

	// 设置游戏状态
	// @J2ME 需要自己画提示信息
	public void setMode(int newMode) {

		// 把当前游戏状态存入oldMode
		int oldMode = mMode;
		// 把游戏状态设置为新状态
		mMode = newMode;

		// 如果新状态是运行状态，且原有状态为不运行，那么就开始游戏
		if (newMode == RUNNING & oldMode != RUNNING) {
			// @J2ME 不显示提示信息
			mStatusText = "";
			// 更新
			update();
			return;
		}

		// 如果新状态是暂停状态，那么设置文本内容为暂停内容
		if (newMode == PAUSE) {
			mStatusText = "暂停中\n按“向上”键恢复";
		}

		// 如果新状态是准备状态，那么设置文本内容为准备内容
		if (newMode == READY) {
			mStatusText = "贪吃蛇\n按“向上”键开始";
		}

		// 如果新状态时失败状态，那么设置文本内容为失败内容
		if (newMode == LOSE) {
			// 把上轮的得分显示出来
			mStatusText = "游戏结束\n得分:" + mScore
					+ "\n按“向上”键开始";
		}
	}

	// 添加苹果
	private void addRandomApple() {
		// 新的坐标
		Coordinate newCoord = null;
		// 防止新苹果出席在蛇身下
		boolean found = false;
		// 没有找到合适的苹果，就在循环体内一直循环，直到找到合适的苹果
		while (!found) {
			// 为苹果再找一个坐标,先随机一个X值
			int newX = 1 + RNG.nextInt(mXTileCount - 2);
			// 再随机一个Y值
			int newY = 1 + RNG.nextInt(mYTileCount - 2);
			// 新坐标
			newCoord = new Coordinate(newX, newY);

			// Make sure it's not already under the snake
			// 确保新苹果不在蛇身下，先假设没有发生冲突
			boolean collision = false;

			int snakelength = mSnakeTrail.size();
			// 和蛇占据的所有坐标比较
			for (int index = 0; index < snakelength; index++) {
				// 只要和蛇占据的任何一个坐标相同，即认为发生冲突了
				if (mSnakeTrail.elementAt(index).equals(newCoord)) {
					collision = true;
				}
			}
			// if we're here and there's been no collision, then we have
			// a good location for an apple. Otherwise, we'll circle back
			// and try again
			// 如果有冲突就继续循环，如果没冲突flag的值就是false,那么自然会退出循环，新坐标也就诞生了
			found = !collision;
		}

		if (newCoord == null) {
			Log.info("Somehow ended up with a null newCoord!");
		}
		// 生成一个新苹果放在苹果列表中（两个苹果有可能会重合——这时候虽然看到的是一个苹果，但是呢，分数就是两个分数。）
		mAppleList.addElement(newCoord);
	}

	// 更新 各种动作，特别是 贪吃蛇 的位置， 还包括：墙、苹果等的更新
	public void update() {
		
		Log.debug("update() mMode="+mMode);
		// 如果是处于运行状态
		if (mMode == RUNNING) {

			long now = System.currentTimeMillis();

			// 如果当前时间距离最后一次移动的时间超过了延迟时间
			if (now - mLastMove > mMoveDelay) {
				//
				clearTiles();
				updateWalls();
				updateSnake();
				updateApples();
				mLastMove = now;
				// @J2ME 强制重绘
				Log.debug("update() 强制重绘");
				repaint();
			}
			// Handler 会话进程
			// @J2ME 游戏必须的循环，用Thread实现
			if(!mRedrawHandler.isAlive())
				mRedrawHandler.start();
		}

	}

	// 更新墙
	private void updateWalls() {
		Log.debug("updateWalls(),mXTileCount="+mXTileCount+",mYTileCount="+mYTileCount);
		for (int x = 0; x < mXTileCount; x++) {
			// 给上边线的每个贴片位置设置一个绿色索引标识
			setTile(GREEN_STAR, x, 0);
			// 给下边线的每个贴片位置设置一个绿色索引标识
			setTile(GREEN_STAR, x, mYTileCount - 1);
		}
		for (int y = 1; y < mYTileCount - 1; y++) {
			// 给左边线的每个贴片位置设置一个绿色索引标识
			setTile(GREEN_STAR, 0, y);
			// 给右边线的每个贴片位置设置一个绿色索引标识
			setTile(GREEN_STAR, mXTileCount - 1, y);
		}
	}

	// 更新苹果
	// @J2ME 用Vector
	private void updateApples() {
		Log.debug("updateApples()");
		for (int i = 0; i<mAppleList.size(); i++) {
			Coordinate c = (Coordinate) mAppleList.elementAt(i);
			setTile(YELLOW_STAR, c.x, c.y);
		}
	}

	// 更新蛇
	// @J2ME 用Vector
	private void updateSnake() {
		Log.debug("updateSnake()");
		// 生长标志
		boolean growSnake = false;

		// 得到蛇头坐标
		Coordinate head = (Coordinate) mSnakeTrail.elementAt(0);
		// 初始化一个新的蛇头坐标
		Coordinate newHead = new Coordinate(1, 1);

		// 当前方向改成新的方向
		mDirection = mNextDirection;

		// 根据方向确定蛇头新坐标
		switch (mDirection) {
		// 如果方向向东（右），那么X加1
		case EAST: {
			newHead = new Coordinate(head.x + 1, head.y);
			break;
		}
			// 如果方向向西（左），那么X减1
		case WEST: {
			newHead = new Coordinate(head.x - 1, head.y);
			break;
		}
			// 如果方向向北（上），那么Y减1
		case NORTH: {
			newHead = new Coordinate(head.x, head.y - 1);
			break;
		}
			// 如果方向向南（下），那么Y加1
		case SOUTH: {
			newHead = new Coordinate(head.x, head.y + 1);
			break;
		}
		}

		// 冲突检测 新蛇头是否四面墙重叠，那么游戏结束
		if ((newHead.x < 1) || (newHead.y < 1) || (newHead.x > mXTileCount - 2)
				|| (newHead.y > mYTileCount - 2)) {
			// 设置游戏状态为Lose
			setMode(LOSE);
			// 返回
			return;

		}

		// 冲突检测 新蛇头是否和自身坐标重叠，重叠的话游戏也结束
		int snakelength = mSnakeTrail.size();

		for (int snakeindex = 0; snakeindex < snakelength; snakeindex++) {
			Coordinate c = (Coordinate) mSnakeTrail.elementAt(snakeindex);
			if (c.equals(newHead)) {
				// 设置游戏状态为Lose
				setMode(LOSE);
				// 返回
				return;
			}
		}

		// 看新蛇头和苹果们是否重叠
		int applecount = mAppleList.size();
		for (int appleindex = 0; appleindex < applecount; appleindex++) {
			Coordinate c = (Coordinate) mAppleList.elementAt(appleindex);
			if (c.equals(newHead)) {
				// 如果重叠，苹果坐标从苹果列表中移除
				mAppleList.removeElement(c);
				// 再立刻增加一个新苹果
				addRandomApple();
				// 得分加一
				mScore++;
				// 延迟是以前的90%
				mMoveDelay *= 0.9;
				// 蛇增长标志改为真
				growSnake = true;
			}
		}

		// 在蛇头的位置增加一个新坐标
		// @J2ME 用Vector插入insertElementAt
		mSnakeTrail.insertElementAt(newHead,0);
		// except if we want the snake to grow
		// 如果没有增长
		if (!growSnake) {
			// 如果蛇头没增长则删去最后一个坐标，相当于蛇向前走了一步
			mSnakeTrail.removeElementAt(mSnakeTrail.size() - 1);
		}

		// 重新设置一下颜色，蛇头是黄色的（同苹果一样），蛇身是红色的
		for (int index = 0; index<mSnakeTrail.size(); index++) {
			Coordinate c = (Coordinate) mSnakeTrail.elementAt(index);
			if (index == 0) {
				setTile(YELLOW_STAR, c.x, c.y);
			} else {
				setTile(RED_STAR, c.x, c.y);
			}
		}
	}

	// 坐标内部类——原作者说这是临时做法
	private class Coordinate {
		public int x;
		public int y;

		// 构造函数
		public Coordinate(int newX, int newY) {
			x = newX;
			y = newY;
		}

		// 重写equals
		public boolean equals(Coordinate other) {
			if (x == other.x && y == other.y) {
				return true;
			}
			return false;
		}

		// 重写toString
		public String toString() {
			return "Coordinate: [" + x + "," + y + "]";
		}
	}
	
	protected void paint(Graphics g) {
		// TODO Auto-generated method stub
		// @J2ME 清屏

		int c0 = g.getColor();
		Font f0 = g.getFont();
		
		Font fh = Font.getFont(Font.FACE_SYSTEM,Font.STYLE_BOLD,Font.SIZE_LARGE);
		
		g.setColor(0x000000);
		g.fillRect(0,0,getWidth(),getHeight());
		Log.debug("SnakeView paint, width="+this.getWidth()+",height="+this.getHeight());
		super.paint(g);
		Log.debug("SnakeView paint, before myself");
		g.setColor(0x8486FF);
		g.setFont(fh);
		drawStatusText(g,mStatusText);
		
		g.setColor(c0);
		g.setFont(f0);
	}
	
	// @J2ME 自己实现mStatusText换行方法（只支持一行、两行或三行）
	private void drawStatusText(Graphics g,String mStatusText) {
		String statusTexts[] = Utils.splitUtil(mStatusText, "\n");
		
		if(statusTexts==null || statusTexts.length==0) {
			return;
		} else if(statusTexts.length==3) {
			g.drawString(statusTexts[0], this.getWidth()/2, this.getHeight()/2-g.getFont().getHeight(), Graphics.TOP | Graphics.HCENTER);
			g.drawString(statusTexts[1], this.getWidth()/2, this.getHeight()/2, Graphics.TOP | Graphics.HCENTER);
			g.drawString(statusTexts[2], this.getWidth()/2, this.getHeight()/2+g.getFont().getHeight(), Graphics.TOP | Graphics.HCENTER);
		} else if(statusTexts.length==2) {
			g.drawString(statusTexts[0], this.getWidth()/2, this.getHeight()/2-g.getFont().getHeight()/2, Graphics.TOP | Graphics.HCENTER);
			g.drawString(statusTexts[1], this.getWidth()/2, this.getHeight()/2+g.getFont().getHeight()/2, Graphics.TOP | Graphics.HCENTER);
		} else {
			g.drawString(mStatusText, this.getWidth()/2, this.getHeight()/2, Graphics.TOP | Graphics.HCENTER);
		}
	}

}
